package time_windown

import (
	"context"
	"sync/atomic"
	"time"
)

// api调用频率控制
type apiRate struct {
	currentFinishCount int32
	maxCount           int32
	ti                 time.Duration
	ch                 chan struct{}
}

func newApiRate(ti time.Duration, maxCount int32) *apiRate {
	res := &apiRate{}
	res.maxCount = maxCount
	res.ti = ti
	res.ch = make(chan struct{})
	return res
}

func (a *apiRate) start(ctx context.Context) {
	go func() {
		ticker := time.NewTicker(a.ti)
		defer ticker.Stop()
		for {
			select {
			case <-ticker.C:
				// 清理频率
				a.reset()

			case <-ctx.Done():
				return
			}
		}
	}()
}

func (a *apiRate) add() {
	if atomic.AddInt32(&a.currentFinishCount, 1) <= a.maxCount {
		// 如果没有超过限制，则放行
		return
	}

	// 如果达到限制，则阻塞直到窗口过期刷新
	a.ch <- struct{}{}
}

func (a *apiRate) reset() {
	// 重置窗口数据
	// - 如果有等待的请求，并且等待数量超过了最大数量maxCount，则允许maxCount个任务执行，后续的新任务还是阻塞
	// - 如果有等待的请求，但是等待数量没有超过maxCount，则执行完"当前时刻"的等待请求后，重置currentFinishCount计数——后续的新任务不会阻塞
	//   （在并发高的时候，如果在执行default，但还没有执行SwapInt32，有请求过来，则会直接阻塞。而SwapInt32执行完成后，后续的请求又不阻塞了。存在这样的状态：有请求在阻塞的同时，currentFinishCount < maxCount. 阻塞的请求会在后一个窗口时间执行）
	// - 如果没有等待的请求，则直接重置currentFinishCount计数——后续的新任务不会阻塞

	for i := 0; i < int(a.maxCount); i++ {
		select {
		case <-a.ch:
			// 放行之前阻塞等待的请求
		default:
			// 如果已经没有阻塞的请求，重置currentFinishCount计数
			atomic.SwapInt32(&(a.currentFinishCount), 0)
			return
		}
	}
}
